import type { AxiosInstance, AxiosRequestConfig } from "axios";
import Axios from "axios";
import { ElNotification } from "element-plus";
import { stringify } from "qs";
import { h } from "vue";
import { authApi } from "@/api";
import { useUserStore } from "@/stores/user";
import { formatToken, getToken, setToken } from "@/utils/auth";
import NProgress from "../progress";

/** 请求白名单, 放置一些不需要`token`的接口（通过设置请求白名单，防止`token`过期后再请求造成的死循环问题） */
const whiteList = [
    "/auth/refreshAccessToken",
    "/auth/signInByUsername",
    "/auth/getSignInCaptcha",
];

/** token 过期后, 暂存待执行的请求 */
let requests: Array<(token: string) => void> = [];

/** 是否正在刷新token, 防止多次刷新token */
let isRefreshing = false;

/** 延时登出, 保证浏览器提示 */
const signout = (): void => {
    setTimeout(() => {
        useUserStore().logout();
    }, 1500);
};

/** 请求超时时间, 单位毫秒 */
const timeout = 9000;

// 相关配置请参考: www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = {
    baseURL: "/api",
    timeout,
    headers: {
        "Accept": "application/json, text/plain, */*",
        "Content-Type": "application/json",
        "X-Requested-With": "XMLHttpRequest",
    },
    paramsSerializer(params) {
        // 参考: https://www.cnblogs.com/small-coder/p/9115972.html
        return stringify(params, { arrayFormat: "repeat" });
    },
};

const http: AxiosInstance = Axios.create(defaultConfig);

// 请求拦截器
http.interceptors.request.use(
    (config) => {
        // 开启进度条
        NProgress.start();

        // get请求添加时间戳, 防止浏览器或中间商缓存
        if (config.method === "get") {
            config.params = {
                _t: Date.now(),
                ...config.params,
            };
        }

        if (whiteList.some(url => config.url!.endsWith(url))) {
            // 在白名单中的请求，不需要判断`token`是否过期
            return config;
        } else { // 不在白名单中的请求，需要判断`token`是否过期
            const token = getToken();
            return new Promise((resolve, reject) => {
                if (token && Object.keys(token).length > 0) {
                    const expires = token.expires;
                    // token过期，需要刷新token，这里冗余超时时间，防止请求耗时导致 token 过期来不及调用 refreshToken 接口
                    if (expires - Date.now() <= timeout) {
                        // 收集过期的请求
                        requests.push((token: string) => {
                            config.headers!.Authorization = formatToken(token);
                            resolve(config);
                        });
                        // 过期请求会在上一步被收集起来，这里isRefreshing为了防止多个过期请求进入导致多次刷新token
                        if (!isRefreshing) {
                            isRefreshing = true;
                            // 刷新`token`，并将所有暂存的请求重新执行
                            authApi.refreshAccessToken(token.refreshToken)
                                .then((resp) => {
                                    if (ok(resp)) {
                                        const { data } = resp;
                                        setToken(data); // 重新设置`token`
                                        config.headers.Authorization = formatToken(
                                            data.accessToken,
                                        ); // 重新设置`Authorization`请求头
                                        requests.forEach(cb => cb(data.accessToken)); // 重连原始请求，即执行所有暂存的请求
                                        resolve(config);
                                    } else {
                                        const { message, errorCode } = resp;
                                        const reason = {
                                            status: errorCode || 401,
                                            message,
                                        };
                                        reject(reason);
                                    }
                                    requests = []; // 清空暂存的请求
                                })
                                .catch(() => {
                                    signout(); // 当刷新token失败时, 执行登出操作
                                    requests = []; // 清空暂存的请求
                                    return new Promise(() => { }); // 返回一个永远不会解析的 Promise, 以阻止原始请求继续执行
                                })
                                .finally(() => {
                                    isRefreshing = false;
                                });
                        }
                    } else {
                        config.headers.Authorization = formatToken(token.accessToken);
                        resolve(config);
                    }
                } else {
                    const unverified = { status: 401, message: "Request failed with status code 401" };
                    reject(unverified);
                }
            });
        }
    },
    (error) => {
        // 关闭进度条
        NProgress.done();

        // 错误提示
        ElNotification.error({
            title: "Request Error",
            message: error.message,
        });

        return Promise.reject(error);
    },
);

// 响应拦截器
http.interceptors.response.use(
    (response) => {
        NProgress.done(); // 关闭进度条
        const { data } = response; // response会有axios其他东西，例如header，data才是后端返回的数据
        return Promise.resolve(data);
    },
    (error) => {
        NProgress.done(); // 关闭进度条

        const errorMsg = error.response?.data?.message || error.message || "Request failed";
        ElNotification({
            title: `${error.status}`,
            message: h("span", { style: "white-space: pre-line;" }, errorMsg),
            type: "error",
        });

        return Promise.reject(error);
    },
);

/** 封装后的请求 */
export default async function request<T = any>(config: AxiosRequestConfig): Promise<T> {
    return http(config);
}
